package cn.rwklyd.BookKeeping.service.impl;

import cn.rwklyd.BookKeeping.constant.MessageConstant;
import cn.rwklyd.BookKeeping.pojo.Permission;
import cn.rwklyd.BookKeeping.pojo.Result;
import cn.rwklyd.BookKeeping.service.TokenService;
import cn.rwklyd.BookKeeping.util.JwtUtils;
import cn.rwklyd.BookKeeping.util.RedisUtils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class TokenServiceImpl implements TokenService {

    @Autowired
    private RedisUtils redisUtils;
    
    // Redis中存储刷新令牌的前缀
    private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
    // Redis中存储用户ID到刷新令牌ID的映射前缀
    private static final String USER_TOKEN_PREFIX = "user_token:";

    @Override
    public Map<String, String> createTokens(Integer userId, String username, String permissionsJson) {
        // 创建JWT负载
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", userId);
        claims.put("username", username);
        claims.put("permissions", permissionsJson);
        
        // 生成访问令牌
        String accessToken = JwtUtils.generateAccessToken(claims);
        
        // 生成唯一标识符作为Redis的key
        String tokenId = UUID.randomUUID().toString();
        
        // 将刷新令牌信息存入Redis
        String redisKey = REFRESH_TOKEN_PREFIX + tokenId;
        Map<String, String> tokenInfo = new HashMap<>();
        tokenInfo.put("userId", userId.toString());
        tokenInfo.put("username", username);
        tokenInfo.put("permissions", permissionsJson);
        
        // 将tokenInfo转为JSON字符串存储
        String tokenInfoJson = JSON.toJSONString(tokenInfo);
        // 注意这里使用的是秒为单位，而JwtUtils中使用的是毫秒
        // 将毫秒转换为秒
        long refreshTokenExpireSeconds = JwtUtils.getRefreshTokenExpireMillis() / 1000;
        redisUtils.set(redisKey, tokenInfoJson, refreshTokenExpireSeconds, TimeUnit.SECONDS);
        
        // 存储用户ID到tokenId的映射，用于查找用户的刷新令牌
        String userTokenKey = USER_TOKEN_PREFIX + userId;
        redisUtils.set(userTokenKey, tokenId, refreshTokenExpireSeconds, TimeUnit.SECONDS);
        
        // 只返回访问令牌
        Map<String, String> tokens = new HashMap<>();
        tokens.put("accessToken", accessToken);
        
        log.info("为用户 {} 创建了新的token", username);
        return tokens;
    }

    @Override
    public Result<Map<String, String>> refreshAccessToken(String accessToken) {
        try {
            // 尝试解析访问令牌，获取用户ID
            Claims claims = JwtUtils.parseJWT(accessToken);
            Integer userId = claims.get("id", Integer.class);
            
            // 访问令牌未过期，无需刷新
            return Result.error("访问令牌未过期，无需刷新");
        } catch (ExpiredJwtException e) {
            // 访问令牌已过期，从异常中获取claims
            Claims claims = e.getClaims();
            Integer userId = claims.get("id", Integer.class);
            
            // 查找用户的刷新令牌ID
            String userTokenKey = USER_TOKEN_PREFIX + userId;
            String tokenId = redisUtils.get(userTokenKey);
            
            if (tokenId == null) {
                log.warn("用户 {} 的刷新令牌不存在", userId);
                return Result.error(700, MessageConstant.REFRESH_TOKEN_EXPIRED);
            }
            
            // 获取刷新令牌信息
            String redisKey = REFRESH_TOKEN_PREFIX + tokenId;
            String tokenInfoJson = redisUtils.get(redisKey);
            
            if (tokenInfoJson == null) {
                log.warn("刷新令牌 {} 已失效", tokenId);
                return Result.error(700, MessageConstant.REFRESH_TOKEN_EXPIRED);
            }
            
            try {
                // 解析刷新令牌信息
                Map<String, String> tokenInfo = JSON.parseObject(tokenInfoJson, new TypeReference<Map<String, String>>() {});
                
                // 创建新的访问令牌
                Map<String, Object> newClaims = new HashMap<>();
                newClaims.put("id", Integer.parseInt(tokenInfo.get("userId")));
                newClaims.put("username", tokenInfo.get("username"));
                newClaims.put("permissions", tokenInfo.get("permissions"));
                
                String newAccessToken = JwtUtils.generateAccessToken(newClaims);
                
                // 返回新的访问令牌
                Map<String, String> result = new HashMap<>();
                result.put("accessToken", newAccessToken);
                
                log.info("用户 {} 的访问令牌已刷新", tokenInfo.get("username"));
                return Result.success(result);
            } catch (Exception ex) {
                log.error("刷新访问令牌时发生错误", ex);
                return Result.error("刷新访问令牌失败");
            }
        } catch (Exception e) {
            log.error("解析访问令牌时发生错误", e);
            return Result.error(MessageConstant.TOKEN_INVALID);
        }
    }

    @Override
    public Result<String> invalidateRefreshToken(Integer userId) {
        try {
            // 查找用户的刷新令牌ID
            String userTokenKey = USER_TOKEN_PREFIX + userId;
            String tokenId = redisUtils.get(userTokenKey);
            
            if (tokenId == null) {
                return Result.error("刷新令牌不存在");
            }
            
            // 删除刷新令牌
            String redisKey = REFRESH_TOKEN_PREFIX + tokenId;
            Boolean deletedToken = redisUtils.delete(redisKey);
            
            // 删除用户ID到tokenId的映射
            Boolean deletedMapping = redisUtils.delete(userTokenKey);
            
            if ((deletedToken != null && deletedToken) || (deletedMapping != null && deletedMapping)) {
                log.info("用户 {} 的刷新令牌已失效", userId);
                return Result.success("登出成功");
            } else {
                return Result.error("刷新令牌已失效或不存在");
            }
        } catch (Exception e) {
            log.error("使刷新令牌失效时发生错误", e);
            return Result.error("登出失败");
        }
    }
} 